package com.xsg.printer.core;

import com.xsg.printer.core.enums.AlignEnum;
import com.xsg.printer.core.enums.ElementTypeEnum;
import com.xsg.printer.core.enums.FloatAlignEnum;
import com.xsg.printer.core.model.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 画笔
 * @param <T>
 */
public abstract class VoucherPaint<T> implements IVoucherPaintAction{

    /**
     * 偏向绘制起始坐标计算函数map
     */
    protected static Map<String, CalStartCoordinate> alignStartCoordinateMap;

    /**
     * 浮向绘制起始坐标计算函数map
     */
    protected static Map<String, CalStartCoordinate> floatAlignStartCoordinateMap;

    protected Map<Integer, DrawElement> drawElementMap;

    protected Map<Integer, ElementSizeCalculator<?>> elementCalSizeMap;

    protected Map<String, Boolean> hasDrawLineMap = new HashMap<>();

    /**
     * 画板容器
     */
    protected final T container;

    public VoucherPaint(T container) {
        this.container = container;
        initAlignCalMap();
        initFloatAlignCalMap();
        initDrawElementMap();
        initElementSizeCalMap();
    }

    private void initAlignCalMap() {
        if (alignStartCoordinateMap != null) {
            return;
        }
        alignStartCoordinateMap = new ConcurrentHashMap<>();
        alignStartCoordinateMap.put(AlignEnum.TOP_LEFT.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) -> limitStart);
        alignStartCoordinateMap.put(AlignEnum.TOP_RIGHT.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX() + (limitWidth - drawWidth)).y(limitStart.getY()).build());
        alignStartCoordinateMap.put(AlignEnum.BOTTOM_LEFT.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX()).y(limitStart.getY() + (limitHeight - drawHeight)).build());
        alignStartCoordinateMap.put(AlignEnum.BOTTOM_RIGHT.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX() + (limitWidth - drawWidth)).y(limitStart.getY() + (limitHeight - drawHeight)).build());
        alignStartCoordinateMap.put(AlignEnum.LR_CENTER.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX() + (limitWidth/2 - drawWidth/2)).y(limitStart.getY()).build());
        alignStartCoordinateMap.put(AlignEnum.TB_CENTER.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX()).y(limitStart.getY() + (limitHeight/2 - drawHeight/2)).build());
        alignStartCoordinateMap.put(AlignEnum.CENTER.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX() + (limitWidth/2 - drawWidth/2)).y(limitStart.getY() + (limitHeight/2 - drawHeight/2)).build());
    }

    private void initFloatAlignCalMap() {
        if (floatAlignStartCoordinateMap != null) {
            return;
        }
        floatAlignStartCoordinateMap = new ConcurrentHashMap<>();
        floatAlignStartCoordinateMap.put(FloatAlignEnum.LEFT.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) -> limitStart);
        floatAlignStartCoordinateMap.put(FloatAlignEnum.RIGHT.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) ->
                XsgCoordinate.builder().x(limitStart.getX() + (limitWidth - drawWidth)).y(limitStart.getY()).build());
        floatAlignStartCoordinateMap.put(FloatAlignEnum.NONE.getCode(), (limitStart, limitWidth, limitHeight, drawWidth, drawHeight) -> limitStart);
    }

    private void initDrawElementMap() {
        drawElementMap = new HashMap<>();
        //文本
        drawElementMap.put(ElementTypeEnum.TEXT.getType(), new DrawElement<XsgElementText>() {
            @Override
            public void draw(XsgElementText element, XsgCoordinate start, int width, int height) {
                drawString(element.getValue(), start, width, height, element.getFont());
            }
        });
        //二维码
        drawElementMap.put(ElementTypeEnum.QR.getType(), new DrawElement<XsgElementQrCode>() {
            @Override
            public void draw(XsgElementQrCode element, XsgCoordinate start, int width, int height) {
                drawQrCode(element.getValue(), start, width, height);
            }
        });

        //条形码
        drawElementMap.put(ElementTypeEnum.BAR.getType(), new DrawElement<XsgElementBarCode>() {
            @Override
            public void draw(XsgElementBarCode element, XsgCoordinate start, int width, int height) {
                drawBarCode(element.getValue(), start, width, height);
            }
        });

        //图片
        drawElementMap.put(ElementTypeEnum.IMAGE.getType(), new DrawElement<XsgElementImage>() {
            @Override
            public void draw(XsgElementImage element, XsgCoordinate start, int width, int height) {
                drawImage(element.getValue(), element.getLocalOrRemote() == 1, start, width, height);
            }
        });

        //圆
        drawElementMap.put(ElementTypeEnum.CIRCLE.getType(), new DrawElement<XsgElementCircle>() {
            @Override
            public void draw(XsgElementCircle element, XsgCoordinate start, int width, int height) {
                drawCircle(element.getValue(), start, width, height, element.getFont());
            }
        });

        //表格
        drawElementMap.put(ElementTypeEnum.SHEET.getType(), new DrawElement<XsgElementSheet>() {
            @Override
            public void draw(XsgElementSheet element, XsgCoordinate start, int width, int height) {
                drawSheet(element.getSheet(), element.getValue(), start, width, height, element.getFont());
            }
        });
    }

    protected abstract void initElementSizeCalMap();

    @Override
    public void drawSheet(XsgSheet sheet, Collection<Collection<?>> value, XsgCoordinate start, int width, int height, XsgFont font) {
        if (sheet == null || sheet.getHeaders() == null || sheet.getHeaders().length <= 0 || value == null || value.isEmpty()) {
            return;
        }
        int cellWidth = sheet.getCellWidth();
        int cellHeight = sheet.getCellHeight();
        Collection<Collection<?>> values = new ArrayList<>();
        values.add(Stream.of(sheet.getHeaders()).collect(Collectors.toList()));
        values.addAll(value);
        int startX = start.getX();
        int startY = start.getY();
        for (Collection<?> row : values) {
            for (Object cell : row) {
                XsgCoordinate cellStart = XsgCoordinate.builder().x(startX).y(startY).build();
                drawFourLines(cellStart, cellWidth, cellHeight, XsgBorder.fullBorder());
                drawString(String.valueOf(cell), cellStart, cellWidth, cellHeight, font);
                startX += cellWidth;
            }
            startY += cellHeight;
            startX = start.getX();
        }
    }

    public void drawTemplate(XsgTemplate template) {
        XsgCoordinate start = XsgCoordinate.builder().x(5).y(5).build();
        //设置整体边框
        drawTemplateLine(template, start);
        //设置标题
        drawHead(template);
        //设置区块
        drawDiv(template.getDivs(), start, template.getWidth(), template.getHeight());
    }

    /**
     * 设置模板整体边框
     * @param template
     */
    private void drawTemplateLine(XsgTemplate template, XsgCoordinate start) {
        //边框样式
        XsgBorder border = template.getBorder();
        if (border == null) {
            border = XsgBorder.hiddenBorder();
        }
        int width = template.getWidth();
        int height = template.getHeight();
        drawFourLines(start, width, height, border);
    }

    /**
     * 设置标题信息
     * @param template
     */
    private void drawHead(XsgTemplate template) {

    }

    /**
     * 设置显示区域
     * @param divs
     */
    private void drawDiv(List<XsgDiv> divs, XsgCoordinate start, int width, int height) {
        if (divs == null || divs.isEmpty()) {
            return;
        }
        int startX = start.getX();
        int startY = start.getY();
        int useWidth = 0;
        int useHeight = 0;
        int maxHeight = 0;
        for (XsgDiv div : divs) {
            if (useHeight > height) {
                break;
            }
            if (div.getWidth() > width - useWidth) {
                useHeight += maxHeight;
                startX = start.getX();
                startY += maxHeight;
                maxHeight = 0;
                useWidth = 0;
            }
            XsgCoordinate divStart = XsgCoordinate.builder().x(startX).y(startY).build();
            CalStartCoordinate calStartCoordinate = floatAlignStartCoordinateMap.get(div.getFloatAlign());
            if (calStartCoordinate != null) {
                divStart = calStartCoordinate.cal(divStart, width - useWidth, height - useHeight, div.getWidth(), div.getHeight());
            }
            drawElements(div.getElements(), divStart, div.getWidth(), div.getHeight());
            drawFourLines(divStart, div.getWidth(), div.getHeight(), div.getBorder());
            if (div.getDivs() != null && !div.getDivs().isEmpty()) {
                drawDiv(div.getDivs(), divStart, div.getWidth(), div.getHeight());
            }
            useWidth += divStart.getX() - startX + div.getWidth();
            startX = divStart.getX() + div.getWidth();
            if (div.getHeight() > maxHeight) {
                maxHeight = div.getHeight();
            }
        }
    }

    public void drawFourLines(XsgCoordinate start, int width, int height, XsgBorder border) {
        XsgCoordinate topLeft = start;
        XsgCoordinate topRight = XsgCoordinate.builder().x(start.getX() + width).y(start.getY()).build();
        XsgCoordinate bottomLeft = XsgCoordinate.builder().x(start.getX()).y(start.getY() + height).build();
        XsgCoordinate bottomRight = XsgCoordinate.builder().x(start.getX() + width).y(start.getY() + height).build();
        this.drawMayBeNoRepeatLine(true, topLeft, topRight, border.getTopStyle(), border.getColor()); //上边框
        this.drawMayBeNoRepeatLine(true, topLeft, bottomLeft, border.getLeftStyle(), border.getColor()); //左边框
        this.drawMayBeNoRepeatLine(true, bottomLeft, bottomRight, border.getBottomStyle(), border.getColor()); //下边框
        this.drawMayBeNoRepeatLine(true, topRight, bottomRight, border.getRightStyle(), border.getColor()); //右边框
    }

    private void drawElements(List<XsgElement> elements, XsgCoordinate start, int width, int height) {
        if (elements == null || elements.isEmpty()) {
            return;
        }
        int startX = start.getX();
        int startY = start.getY();
        int useWidth = 0;
        int useHeight = 0;
        int maxHeight = 0;
        for (XsgElement element : elements) {
//            ElementSizeCalculator calculator = getElementSizeCalculator(element);
//            int eleWidth = calculator.width(element);
//            int eleHeight = calculator.height(element);
            int eleWidth = element.getWidth();
            int eleHeight = element.getHeight();
            if (useHeight > height) {
                break;
            }
            if (eleWidth > width - useWidth) {
                useHeight += maxHeight;
                startX = start.getX();
                startY += maxHeight;
                maxHeight = 0;
                useWidth = 0;
            }
            XsgMargin margin = element.getMargin() == null ? XsgMargin.defaultMargin() : element.getMargin();
            XsgCoordinate eleStart = XsgCoordinate.builder().x(startX + margin.getLeft()).y(startY + margin.getTop()).build();
            //重新计算起始坐标
            CalStartCoordinate calStartCoordinate = alignStartCoordinateMap.get(element.getAlign());
            if (calStartCoordinate != null) {
                eleStart = calStartCoordinate.cal(eleStart, width - useWidth - margin.getLeft() - margin.getRight(), height - useHeight - margin.getTop() - margin.getBottom(), eleWidth, eleHeight);
            }
            DrawElement drawElement = drawElementMap.get(element.getType());
            drawElement.draw(element, eleStart, eleWidth, eleHeight);
            useWidth += eleStart.getX() - startX + eleWidth + margin.getRight();
            startX = eleStart.getX() + eleWidth + margin.getRight();
            if (eleHeight + margin.getTop() + margin.getBottom() > maxHeight) {
                maxHeight = eleHeight + margin.getTop() + margin.getBottom();
            }
        }
    }

    public ElementSizeCalculator<?> getElementSizeCalculator(XsgElement element) {
        return elementCalSizeMap != null ? elementCalSizeMap.get(element.getType()) : null;
    }

    public void drawMayBeNoRepeatLine(boolean mayBe, XsgCoordinate start, XsgCoordinate end, int borderType, int color) {
        if (borderType == 0) {
            return;
        }
        if (mayBe) {
            String key = start.toString() + end.toString();
            Boolean hasDraw = hasDrawLineMap.get(key);
            if (hasDraw != null && hasDraw) {
                return;
            } else {
                hasDrawLineMap.put(key, Boolean.TRUE);
            }
        }
        drawLine(start, end, borderType, color);
    }

    @FunctionalInterface
    public interface CalStartCoordinate {
        XsgCoordinate cal(XsgCoordinate limitStart, int limitWidth, int limitHeight, int drawWidth, int drawHeight);
    }

    @FunctionalInterface
    public interface DrawElement<T extends XsgElement> {
        void draw(T element, XsgCoordinate start, int width, int height);
    }

    public interface ElementSizeCalculator<T extends XsgElement> {
        int width(T element);
        int height(T element);
    }
}
